Skip to content

feat: add ordinal gauge dimension column#26

Merged
abnegate merged 1 commit into
mainfrom
feat/gauge-ordinal-dimension
Jul 15, 2026
Merged

feat: add ordinal gauge dimension column#26
abnegate merged 1 commit into
mainfrom
feat/gauge-ordinal-dimension

Conversation

@abnegate

Copy link
Copy Markdown
Member

What

Adds ordinal as a gauge-only dimension column: GAUGE_COLUMNS, gauge schema, gauge indexes (set(0) skipping index), LowCardinality(Nullable(String)) column type, typed Metric::getOrdinal() accessor, and the appended column in the single-collection Database adapter.

Why

Multi-node resources (e.g. dedicated database StatefulSets) report one gauge sample per pod. Without a distinguishing dimension, replica rows for the same (metric, resourceId) collapse into one argMax series and corrupt each other. ordinal carries the pod ordinal (0 = primary, 1+ = replicas) so per-replica series stay distinct and are queryable via equal('ordinal', [...]) filters and groupBy('ordinal').

Consumed by appwrite-labs/cloud for flat dedicated-database gauge metrics (DAT-1880), replacing .replicaN metric-name suffixes.

Migration

Existing ClickHouse gauge tables gain the column automatically via ensureDimColumns (ADD COLUMN IF NOT EXISTS) at setup(). The skipping index applies to newly created tables only.

Tests

  • testOrdinalIsGaugeOnly — column set/schema/index/extract/getter coverage, event schema unaffected
  • testGaugeOrdinalSeparatesReplicaSeries — live ClickHouse round-trip: per-ordinal filter + groupBy
  • testGaugeColumnsRoundTrip / testLowCardinalityOrdinalColumn extended

Full suite: 261 tests, 1281 assertions green in the compose stack. Lint + PHPStan clean.

🤖 Generated with Claude Code

Multi-node resources (e.g. dedicated database StatefulSets) report one
gauge sample per pod. Without a distinguishing dimension, replica rows
for the same (metric, resourceId) collapse into one argMax series and
corrupt each other. The ordinal column carries the pod ordinal (0 is
the primary) so per-replica series stay distinct and remain queryable
via filters and groupBy.

Existing gauge tables gain the column automatically through
ensureDimColumns (ADD COLUMN IF NOT EXISTS) at setup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 15, 2026 05:34

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@greptile-apps

greptile-apps Bot commented Jul 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces ordinal as a gauge-only dimension column to distinguish per-replica metric series for multi-node resources (e.g. StatefulSet pods), preventing argMax rows from collapsing across replicas that share the same (metric, resourceId).

  • Metric.php: ordinal added to GAUGE_COLUMNS, gauge schema (LowCardinality(Nullable(String))), gauge indexes (set(0) skip index), and a typed getOrdinal(): ?string accessor. Event schema is intentionally unaffected.
  • ClickHouse.php: ordinal appended to the lowCardinality list so existing tables gain the column via ADD COLUMN IF NOT EXISTS during setup().
  • Database.php: ordinal attribute and index-ordinal appended to the collection definition, but only take effect on first createCollection()—no backfill path exists for existing collections unlike the ClickHouse adapter's ensureDimColumns mechanism.

Confidence Score: 3/5

Safe to merge for ClickHouse deployments; existing Database adapter collections will not receive the new column on upgrade.

The ClickHouse path is well-covered: ensureDimColumns backfills the column on existing tables, live round-trip and schema tests pass, and the index type choice (set(0) on a handful of distinct ordinal values) is appropriate. The gap is in the Database adapter: setup() only calls createCollection(), which is a no-op on an existing collection, so the ordinal attribute and its index are never added. There is no createAttribute/createIndex fallback call analogous to the ClickHouse ADD COLUMN IF NOT EXISTS. Writing a gauge metric with ordinal set against an upgraded Database adapter deployment will either fail or silently drop the field.

src/Usage/Adapter/Database.php — the migration gap for existing collections needs a backfill path comparable to ensureDimColumns.

Important Files Changed

Filename Overview
src/Usage/Adapter/ClickHouse.php Adds ordinal to the lowCardinality list in getColumnType(), giving it LowCardinality(Nullable(String)) — consistent with how other low-cardinality gauge dimensions are typed. No other logic is affected.
src/Usage/Adapter/Database.php Appends the ordinal attribute and index-ordinal to the collection schema, but only during initial createCollection(). Existing collections silently skip the migration because DuplicateException is caught without any attribute-backfill logic.
src/Usage/Metric.php Adds ordinal to GAUGE_COLUMNS, gauge schema, gauge indexes (with set(0) skip index), and a typed getOrdinal(): ?string accessor. All changes are internally consistent.
tests/Usage/Adapter/ClickHouseTest.php Extends testGaugeColumnsRoundTrip with ordinal and adds testGaugeOrdinalSeparatesReplicaSeries, a live ClickHouse round-trip proving per-ordinal filter and groupBy return distinct series.
tests/Usage/Adapter/ClickHouseSchemaTest.php Adds ordinal to the lowCardinality list in expectedDimAssertions. The gauge index test (testGaugesTableSwapsBloomForSetOnLowCardinality) is not extended to assert the new set(0) skip index for ordinal.
tests/Usage/Adapter/ClickHouseColumnTypeTest.php Updates columnType() helper to accept a $type parameter and adds testLowCardinalityOrdinalColumn verifying the ClickHouse column type mapping for ordinal.
tests/Usage/MetricTest.php Adds testOrdinalIsGaugeOnly covering constant membership, schema/index presence, extractColumns coercion, and getOrdinal() accessor. testGaugeColumnsConstant updated to include ordinal.

Comments Outside Diff (2)

  1. src/Usage/Adapter/Database.php, line 117-125 (link)

    P1 No migration path for existing Database adapter collections

    The ordinal attribute and its index are appended to the $attributes / $indexDocs arrays before createCollection(), but on any deployment where the collection already exists the DuplicateException is silently swallowed and neither the attribute nor the index is ever added. The ClickHouse adapter handles this explicitly via ensureDimColumns (ADD COLUMN IF NOT EXISTS), but there is no equivalent "ensure attribute" call here. Attempting to write a gauge metric with ordinal set against an existing collection will either fail or silently drop the field depending on how the underlying utopia-php/database library handles undeclared attributes.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/Usage/Adapter/Database.php
    Line: 117-125
    
    Comment:
    **No migration path for existing Database adapter collections**
    
    The `ordinal` attribute and its index are appended to the `$attributes` / `$indexDocs` arrays before `createCollection()`, but on any deployment where the collection already exists the `DuplicateException` is silently swallowed and neither the attribute nor the index is ever added. The ClickHouse adapter handles this explicitly via `ensureDimColumns` (`ADD COLUMN IF NOT EXISTS`), but there is no equivalent "ensure attribute" call here. Attempting to write a gauge metric with `ordinal` set against an existing collection will either fail or silently drop the field depending on how the underlying utopia-php/database library handles undeclared attributes.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code Fix in Codex

  2. tests/Usage/Adapter/ClickHouseSchemaTest.php, line 114-122 (link)

    P2 ordinal skip-index assertion missing from gauge schema test

    testGaugesTableSwapsBloomForSetOnLowCardinality verifies that service and resourceType use set(0) while resourceId and teamId use bloom_filter, but it does not assert that the new index-ordinal was created with TYPE set(0). Adding assertStringContainsString('index-ordinal ordinal TYPE set(0)', $ddl) would give the same live-DDL coverage that the existing index assertions provide.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: tests/Usage/Adapter/ClickHouseSchemaTest.php
    Line: 114-122
    
    Comment:
    **`ordinal` skip-index assertion missing from gauge schema test**
    
    `testGaugesTableSwapsBloomForSetOnLowCardinality` verifies that `service` and `resourceType` use `set(0)` while `resourceId` and `teamId` use `bloom_filter`, but it does not assert that the new `index-ordinal` was created with `TYPE set(0)`. Adding `assertStringContainsString('`index-ordinal` ordinal TYPE set(0)', $ddl)` would give the same live-DDL coverage that the existing index assertions provide.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

    Fix in Claude Code Fix in Codex

Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
src/Usage/Adapter/Database.php:117-125
**No migration path for existing Database adapter collections**

The `ordinal` attribute and its index are appended to the `$attributes` / `$indexDocs` arrays before `createCollection()`, but on any deployment where the collection already exists the `DuplicateException` is silently swallowed and neither the attribute nor the index is ever added. The ClickHouse adapter handles this explicitly via `ensureDimColumns` (`ADD COLUMN IF NOT EXISTS`), but there is no equivalent "ensure attribute" call here. Attempting to write a gauge metric with `ordinal` set against an existing collection will either fail or silently drop the field depending on how the underlying utopia-php/database library handles undeclared attributes.

### Issue 2 of 2
tests/Usage/Adapter/ClickHouseSchemaTest.php:114-122
**`ordinal` skip-index assertion missing from gauge schema test**

`testGaugesTableSwapsBloomForSetOnLowCardinality` verifies that `service` and `resourceType` use `set(0)` while `resourceId` and `teamId` use `bloom_filter`, but it does not assert that the new `index-ordinal` was created with `TYPE set(0)`. Adding `assertStringContainsString('`index-ordinal` ordinal TYPE set(0)', $ddl)` would give the same live-DDL coverage that the existing index assertions provide.

Reviews (1): Last reviewed commit: "feat: add ordinal gauge dimension column" | Re-trigger Greptile

@abnegate
abnegate merged commit 09f2ea4 into main Jul 15, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants